/**
 * @file usermenu.c
 * @author your name (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2022-09-08
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <common.h>
#include <cli.h>
#include <command.h>
#include <console.h>
#include <search.h>
#include <ansi.h>
#include <menu.h>
#include <watchdog.h>
#include <malloc.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <net.h>

#include "usermenu_update_image.h"

#define USERMENU_MAX_LINE			255	///< 命令行最大长度
#define USERMENU_PROMPT_LEN			50	///< 提示符最大长度

static unsigned char usermenu_prompt[USERMENU_PROMPT_LEN] = {0};

static char *helpMsg[] = {
	"?", 			"- Print this list",
	"@",			"- Boot (load and go) in memory",
	"c",       		"- Change u-boot common env",
	"j",            "- Jump to u-boot command line",
	"m",            "- Modify the mac address in the environment variable",
	"p",            "- Print the current configuration information",
	"r", 			"- Reboot",
	"usu", 			"- Upgrade SD Card u-boot",
	"usd", 			"- Upgrade SD Card device tree",
	"usk", 			"- Upgrade SD Card kernel",
	"usr", 			"- Upgrade SD Card rootfs",
	"usa",			"- Upgrade SD Card image",
	"ueu", 			"- Upgrade eMMC u-boot",
	"ued", 			"- Upgrade eMMC device tree",
	"uek", 			"- Upgrade eMMC kernel",
	"uer", 			"- Upgrade eMMC rootfs",
	"uea",			"- Upgrade eMMC image",
	"h", 			"- Print help",
	NULL
};

static void usermenu_print_help(void)
{
	char **pMsg;

	printf("\n");
	for(pMsg = helpMsg; NULL != *pMsg; pMsg += 2)
		printf(" %-21s 		%s\n", *pMsg, *(pMsg + 1));
	printf("\n");
}

extern struct hsearch_data env_htab;

/**
 * @brief 打印环境变量
 *
 * @param name
 * @return int
 */
static int usermenu_print_env(char *name)
{
	char *res = NULL;
	size_t len;

	if(name) {
		struct env_entry e, *ep;

		e.key = name;
		e.data = NULL;
		hsearch_r(e, ENV_FIND, &ep, &env_htab, 0);
		if(NULL == ep) {
			printf("%-13s : ", e.key);
			return 0;
		}
		len = printf("%-13s : %s ", ep->key, ep->data);
		return len;
	}

	/** print whole list */
	len = hexport_r(&env_htab, '\n', 0, &res, 0, 0, NULL);
	if(len > 0) {
		puts(res);
		free(res);
		return len;
	}

	/* should never happen */
	printf("## Error: cannot export environment\n");
	return 0;
}

/** @brief 	c 命令，用于更改常用的环境变量 */
static void usermenu_env_change(void)
{
	char cmdLine[USERMENU_MAX_LINE] = {0};
	int len = 0;

	memset(usermenu_prompt, 0, USERMENU_PROMPT_LEN);
	printf("\n");

	usermenu_print_env("bootdelay");
	len = cli_readline((const char *)usermenu_prompt);
	if(len > 0) {
		strcpy(cmdLine, console_buffer);
		env_set("bootdelay", cmdLine);
	}

	usermenu_print_env("ipaddr");
	len = cli_readline((const char *)usermenu_prompt);
	if(len > 0) {
		strcpy(cmdLine, console_buffer);
		env_set("ipaddr", cmdLine);
		net_ip = string_to_ip(cmdLine);
	}

	usermenu_print_env("gatewayip");
	len = cli_readline((const char *)usermenu_prompt);
	if(len > 0) {
		strcpy(cmdLine, console_buffer);
		env_set("gatewayip", cmdLine);
		net_gateway = string_to_ip(cmdLine);
	}

	usermenu_print_env("netmask");
	len = cli_readline((const char *)usermenu_prompt);
	if(len > 0) {
		strcpy(cmdLine, console_buffer);
		env_set("netmask", cmdLine);
		net_netmask = string_to_ip(cmdLine);
	}

	usermenu_print_env("serverip");
	len = cli_readline((const char *)usermenu_prompt);
	if(len > 0) {
		strcpy(cmdLine, console_buffer);
		env_set("serverip", cmdLine);
		/** 网络的 server ip 没有立即生效，需要执行此步骤 */
		net_server_ip = string_to_ip(cmdLine);
	}

	usermenu_print_env("u-boot");
	len = cli_readline((const char *)usermenu_prompt);
	if(len > 0) {
		strcpy(cmdLine, console_buffer);
		env_set("u-boot", cmdLine);
	}

	usermenu_print_env("boot_file");
	len = cli_readline((const char *)usermenu_prompt);
	if(len > 0) {
		strcpy(cmdLine, console_buffer);
		env_set("boot_file", cmdLine);
	}

	usermenu_print_env("fdt_file");
	len = cli_readline((const char *)usermenu_prompt);
	if(len > 0) {
		strcpy(cmdLine, console_buffer);
		env_set("fdt_file", cmdLine);
	}

	usermenu_print_env("rootfs_file");
	len = cli_readline((const char *)usermenu_prompt);
	if(len > 0) {
		strcpy(cmdLine, console_buffer);
		env_set("rootfs_file", cmdLine);
	}

	usermenu_print_env("image");
	len = cli_readline((const char *)usermenu_prompt);
	if(len > 0) {
		strcpy(cmdLine, console_buffer);
		env_set("image", cmdLine);
	}

	printf("\n");
	env_save();
}

#define MAC_ADDR_LEN 				6
#define MAC_ADDR_STR_LEN			17
#define MAC_ADDR_DEFAULT_STR		"b8:ae:1d:01:00:00"

/**
 * @brief  找到 mac 地址的第一个字母或数字的地址
 * @note
 * @param  *str: 命令行字符串
 * @retval 成功，返回 mac 地址字符串的首地址
 * 		   失败，返回 NULL
 */
char *mac_address_get(char *str)
{
	int i;
	int len;

	if(NULL == str) return NULL;

	len = strlen(str);

	for(i = 0; i < len; i++) {
		if(isalnum(str[i]))
			return &str[i];
	}

	return NULL;
}

/**
 * @brief
 *
 * @param s
 * @return int
 */
static int usermenu_mac_change(char *s)
{
	char *p = NULL;

	if(NULL == s) return -1;

	p = mac_address_get(s);
	if(NULL == p) {
		/** 输出当前的 mac 地址 */
		p = env_get("ethaddr");
		if(NULL == p) {
			/** 获取不到 mac 地址，则设置默认一个默认 MAC 地址 */
			strcpy(p, MAC_ADDR_DEFAULT_STR);
			env_set("ethaddr", p);
		}

		printf("mac address: %s\n", p);
		return 0;
	}

	if(MAC_ADDR_STR_LEN != strlen(p)) {
		printf("mac str len error.\n");
		return -1;
	}

	env_set("ethaddr", p);
	printf("mac address: %s\n", p);
	env_save();

	return 0;
}

/**
 * @brief
 *
 */
static void usermenu_env_show(void)
{
	printf("\n");
	usermenu_print_env("bootdelay");
	printf("\n");
	usermenu_print_env("ipaddr");
	printf("\n");
	usermenu_print_env("gatewayip");
	printf("\n");
	usermenu_print_env("netmask");
	printf("\n");
	usermenu_print_env("serverip");
	printf("\n");
	usermenu_print_env("ethaddr");
	printf("\n");
	usermenu_print_env("u-boot");
	printf("\n");
	usermenu_print_env("boot_file");
	printf("\n");
	usermenu_print_env("fdt_file");
	printf("\n");
	usermenu_print_env("image");
	printf("\n\n");
}

static UPDATE_IMAGE_TYPE_ET usermenu_check_image_type(char *c)
{
	switch (*c) {
		case 'u':	///< 升级 u-boot
			return IMAGE_IS_UBOOT;
		case 'd':	///< 升级 设备树
			return IMAGE_IS_DTB;
		case 'k':	///< 升级 内核
			return IMAGE_IS_KERNEL;
		case 'r':	///< 升级 文件系统
			return IMAGE_IS_ROOTFS;
		case 'a':	///< 升级 全镜像
			return IMAGE_IS_ALL;
	}
	return IMAGE_IS_UNKOWN;
}

/**
 * @brief
 *
 * @param cmdtp
 * @param flag
 * @param argc
 * @param argv
 * @return int
 */
static int usermenu_proc(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
	int ret = -1;
	char line[USERMENU_MAX_LINE] = {0};
	char *cmdline = NULL;
	int len = 0;
	UPDATE_IMAGE_TYPE_ET image_type; ///< 存储设备类型
	char c;

	ret = update_image_init();
	if(ret < 0) {
		printf("upgrade image init error.\n");
		return -1;
	}

	memset(usermenu_prompt, 0, USERMENU_PROMPT_LEN);
	while(1) {
		sprintf((char *)usermenu_prompt, "%s", "[linux boot] ==> ");
		len = cli_readline((char *)usermenu_prompt);

		if(len <= 0)
			continue;

		strcpy(line, console_buffer);
		cmdline = line;

		switch(*(cmdline++)) {
			case 'c': 	///< 修改配置
				usermenu_env_change();
				break;
			case 'j':	///< 进入到 uboot 命令行
				return 0;
			case 'm':	///< 添加或修改 mac 地址
				usermenu_mac_change(cmdline++);
				break;
			case 'p':	///< 打印配置信息
				usermenu_env_show();
				break;
			case 'r':	///< 重启设备
				run_command("reset", 0);
				break;
			case 't':	///< 只是用于测试某些函数
				break;
			case '@':	///< 烧写 image 到内存
				printf("Prepare update image to memory.\n");
				usermenu_update_image_proc(BLK_DEV_MEMORY, IMAGE_IS_ALL);
				break;
			case 'u':	/** 烧写镜像 */
				switch(*(cmdline++)) {
					case 'e':
						printf("Prepare update image to eMMC.\n");
						strncpy(&c, cmdline, 1);
						image_type = usermenu_check_image_type(&c);
						ret = usermenu_update_image_proc(BLK_DEV_EMMC, image_type);
						if(ret < 0)
							printf("Update image to eMMC failed.\n");
						break;
					case 's':
						printf("Prepare update image to SD Card.\n");
						strncpy(&c, cmdline, 1);
						image_type = usermenu_check_image_type(&c);
						ret = usermenu_update_image_proc(BLK_DEV_SD, image_type);
						if(ret < 0)
							printf("Update image to SD Card failed.\n");
						break;
					case 'n':
						printf("Prepare update image to nand flash.\n");
						strncpy(&c, cmdline, 1);
						image_type = usermenu_check_image_type(&c);
						ret = usermenu_update_image_proc(BLK_DEV_NAND_FLASH, image_type);
						if(ret < 0)
							printf("Update image to Nand Flash failed.\n");
						break;
					case 'N':
						printf("Prepare update image to Nor flash.\n");
						strncpy(&c, cmdline, 1);
						image_type = usermenu_check_image_type(&c);
						ret = usermenu_update_image_proc(BLK_DEV_NOR_FLASH, image_type);
						if(ret < 0)
							printf("Update image to Nor Flash failed.\n");
						break;
					default:
						printf("Unknown upgrade command\n");
						break;
				}
				break;
			case '?':
			case 'h':	/** help */
				usermenu_print_help();
				break;
			default:
				usermenu_print_help();
				break;
		}
	}

	// boot_dev_list_destroy(&boot_dev_list);

	return 0;
}

/**
 * @brief
 *
 * @param cmdtp
 * @param flag
 * @param argc
 * @param argv
 * @return int
 */
int do_usermenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
	usermenu_proc(cmdtp, flag, argc, argv);
	return 0;
}

U_BOOT_CMD(
	usermenu, 3, 0, do_usermenu,
	"Display a user menu to select the items\n",
	"[delay]\n"
	"    - show user menu"
);
